<template>
  <div>
    <a-modal
      :title="'编辑自定义算子'"
      :visible="visible"
      :dialog-style="{ top: '29px' }"
      :width="currentWidth"
      :height="modalHeight"
      :z-index="1051"
      :mask-closable="false"
      centered
      ok-text="运行算子"
      :destroy-on-close="true"
      cancel-text="取消"
      @ok="handleOk"
      @cancel="handleCancel"
    >
      <div class="configure-area">
        <div class="name-area" @click="isEditingOperatorName = renameEnabled">
          <a-popover
            v-if="!isEditingOperatorName"
            placement="top"
            :overlay-style="{ zIndex: 1051 }"
          >
            <template slot="content">
              <span>{{ notebookName }}</span>
            </template>
            <span class="operator-name">{{ notebookName }}</span>
          </a-popover>
          <a-input
            v-else
            auto-focus
            size="small"
            style="height: 22px; width: 100%;"
            :value="notebookName"
            @blur="onNameChange($event)"
            @pressEnter="($event) => $event.target.blur()"
          />
          <a-icon-font
            v-if="!isEditingOperatorName"
            class="edit-btn"
            type="iconicon-beifen6"
          />
          <span v-show="!isEditingOperatorName" class="save-time-box">{{
            saveTimeline
          }}</span>
        </div>
        <!-- <div class="interpreter">
          <span>解释器</span>
          <a-select v-model="interpreter" size="small" class="selector" @change="runnerHandleChange">
            <a-select-option value="python">python</a-select-option>
            <a-select-option value="spark">spark</a-select-option>
            <a-select-option value="madlib">madlib</a-select-option>
          </a-select>
        </div> -->
        <div class="right-menu">
          <div class="edit-operator-tips">
            <a-popover
              title=""
              :overlay-style="{ width: '300px', height: '500px', zIndex: 1051 }"
            >
              <template slot="content">
                <span>系统函数：</span>
                <br />
                <span>
                  1. 给sourceTable赋值时，键入$ 选择需要处理的节点数据
                </span>
                <br />
                <span>
                  2. load_df_spark(sourceTable) 将数据读取为spark数据集
                </span>
              </template>
              <a-icon-font type="iconbangzhu" />
            </a-popover>
          </div>
          <span>编程语言</span>
          <a-select
            v-model="languageType"
            size="small"
            class="selector"
            :dropdown-style="{ zIndex: 1051 }"
            @change="typeHandleChange"
          >
            <a-select-option value="python">python</a-select-option>
            <a-select-option value="text/x-rsrc" disabled>r</a-select-option>
            <a-select-option value="text/x-mysql" disabled>sql</a-select-option>
          </a-select>
        </div>
      </div>
      <div class="icon-area left-btn" @click.stop>
        <a-tooltip lacement="top">
          <template slot="title">
            <span>运行</span>
          </template>
          <div class="btn function-btn-spacing" @click="operatorRunAll">
            <a-icon-font type="iconyunhang" />
          </div>
        </a-tooltip>
        <span class="divider"></span>
        <a-tooltip
          v-for="(tip, index) in functionList"
          :key="index"
          placement="top"
        >
          <template slot="title">
            <span>{{ tip.tooltip }}</span>
          </template>
          <div
            class="btn function-btn-spacing"
            @click="clickFunction(tip.click)"
          >
            <a-icon-font :type="tip.icon" />
          </div>
        </a-tooltip>
        <span class="divider"></span>
        <a-tooltip placement="top">
          <template slot="title">
            <span>删除</span>
          </template>
          <div class="btn function-btn-spacing" @click="iconDeleteCell">
            <a-icon-font type="iconicon-beifen5" />
          </div>
        </a-tooltip>
        <span class="divider"></span>
        <a-popover
          title="鼠标快捷键"
          placement="rightTop"
          :overlay-style="{ width: '450px', zIndex: 1051 }"
        >
          <template slot="content">
            <p>【<a-icon-font type="iconshangxiayidong" />】上下移动</p>
            <p>【Ctrl+C】复制</p>
            <p>【Ctrl+V】粘贴</p>
            <p>【Ctrl+X】剪切</p>
            <p>【DD】删除当前代码块</p>
            <p>【Ctrl+Z】撤销代码块操作</p>
            <p>【Shift+Ctrl+Z】重做代码块操作</p>
            <p>【Ctrl+Enter】运行当前代码块/总体运行</p>
            <p>【Ctrl+ArrowUp】运行当前代码块之前的所有代码块</p>
            <p>【Ctrl+ArrowDown】运行当前代码块以及其后的所有单元格</p>
            <p>【Ctrl+A】全选</p>
            <p>【Ctrl+/】注释代码</p>
            <p>【Shift+Ctrl+K】删除当前行</p>
            <p>【Ctrl+R】替换</p>
          </template>
          <div class="btn function-btn-spacing">
            <a-icon-font type="iconkuaijiejian" />
          </div>
        </a-popover>
      </div>
      <div
        class="divider-line"
        :class="{ 'progress-bar': runningStatus }"
      ></div>
      <div class="editor-area">
        <div class="cell-box">
          <transition-group name="flip-list">
            <code-cell-editor
              v-for="(cell, index) in cells"
              ref="cells"
              :key="cell.id"
              :value="cell.value"
              :result="cell.result"
              :para-id="cell.paraId"
              :data-node-id="cell.id"
              :result-type="cell.resultType"
              :cell-status="cell.cellStatus"
              :theme="theme"
              :mode="languageType"
              :can-delete="canDelete"
              :selected-cell="selectedCell"
              @update-value="updateValue(index, $event)"
              @add-cell="addCell(cell.id, $event)"
              @paste-cell="pasteCell"
              @delete-cell="deleteCell(cell.paraId, index)"
              @run-code-cell="runCodeCell(index, cell.paraId, cell.value)"
              @drag-start="($event) => dragCellstart($event, index)"
              @drag-enter="($event) => dragCellenter($event, index)"
              @drag-end="dragCellend()"
              @editor-click="onEditorClick(cell.paraId)"
              @editor-focus="editorFocus = true"
              @editor-blur="editorFocus = false"
              @set-cell-status-running="setCellStatusState(cell, '运行中...')"
              @set-cell-status-finished="setCellStatusState(cell, '运行完成')"
            >
            </code-cell-editor>
          </transition-group>
        </div>
      </div>
    </a-modal>
  </div>
</template>

<script lang="ts">
import Vue from 'vue'
import Component from 'vue-class-component'
import { Prop, Watch } from 'vue-property-decorator'
import {
  createParagraph,
  deleteParagraph,
  updateParagraphs,
  queryNotebook,
  runParagraph,
  runNotebook,
  queryRunningStatus,
  moveParagraph,
  renameNotebook,
  exportOperator,
  stopPara,
  stopAllPara,
  runUpOrDownPara,
  undo,
  redo,
} from '@/api/edit-operator'
import CodeCellEditor from '@/components/studio/data/popup/CodeCellEditor.vue'
import DataStore, { EditActionArea } from '@/store/modules/dataview'
import { simpleUpdate } from '@/api/task'
import { debounce } from 'lodash'

@Component({
  components: {
    CodeCellEditor,
  },
})
export default class OperatorEditor extends Vue {
  @Prop() visible!: boolean
  @Prop() operatorId!: number
  @Prop() taskId!: number

  public get currentWidth(): number {
    return document.body.clientWidth
  }
  public get modalHeight(): number {
    return document.body.clientHeight - 64
  }

  public get pipelineId() {
    return DataStore.getPipelineId
  }

  //  记录cell 是否可删除，当cell length = 1 不可删除， 为false
  public canDelete: boolean = false
  public renameEnabled: boolean = true
  private isEditingOperatorName: boolean = false
  public functionList: any[] = [
    { tooltip: '停止', click: 'stop', icon: 'iconfuzhibeifen' },
    { tooltip: '复制', click: 'copy', icon: 'iconfuzhijiedian' },
    { tooltip: '粘贴', click: 'paste', icon: 'iconniantie' },
    { tooltip: '保存', click: 'save', icon: 'iconbaocun-copy' },
  ]
  //  editor theme
  public theme: string = 'base16-light'
  //  operator name - 后端返回，可更改
  public notebookName: string = '' //  `算子 - ${ new Date().toISOString() }`
  // public interpreter: string = ''
  public languageType: string = ''
  public cells: any = []

  // 新增cell id 逐级累加
  public id: number = 1

  //  拖拽时记录相关cell 的index
  public oldValue: number = 0
  public newOld: number = 0

  //  后端请求所需notebookId, id
  public notebookId: string = ''

  //  自动保存的timer
  public autoSaveTimer: any
  //  获取全部运行后cell 状态的timer
  public queryTimer: any
  //  全量/cell 运行
  public runningStatus: boolean = false
  //  editor 是否处于focus 状态
  public editorFocus: boolean = false

  //  获取vuex 中的复制的cell value
  public get _copiedCodeValue() {
    return DataStore.CopiedCellValue
  }

  private get editNode() {
    return DataStore.editOperatorNode || {}
  }
  //  记录keyboard 上一次的key
  public lastKey: string = ''

  public saveTimeline: string = ''
  public runUpAndDownFlag: boolean = false

  public get actionArea() {
    return DataStore.ActionArea
  }

  //  当modal visible, 监听.ant-modal-body click 事件，为了清除选中cell的聚焦
  @Watch('visible')
  public _onVisibleChange(visible: boolean) {
    if (visible) {
      this.$nextTick(() => {
        ;(document.querySelector(
          '.ant-modal-body'
        ) as HTMLDivElement).addEventListener('click', this.clearSelectedCell)
        this.loadNotebook(this.operatorId)
        DataStore.getTableList()
      })
      document.addEventListener('keydown', this.globalKeyDown)
      this.autoSaveCode()
      DataStore.setActionArea(EditActionArea.OPERATOR)
    } else {
      document.removeEventListener('keydown', this.globalKeyDown)
      clearInterval(this.autoSaveTimer)
      DataStore.setActionArea(EditActionArea.PIPELINE)
    }
  }

  public beforeDestroy() {
    document.removeEventListener('click', this.clearSelectedCell)
  }
  //  键盘监听事件
  public globalKeyDown($event: KeyboardEvent) {
    //  当cm focus 不处理以下监听逻辑
    //  window.getSelection() -> 当选择文本时，使用默认的行为，比如复制
    if (
      !this.editorFocus &&
      this.actionArea === 'edit-operator' &&
      // @ts-ignore
      window.getSelection().toString().length === 0
    ) {
      let isAllowedToSetLastKey = true
      //  获取focus cell
      const cell = (this.$refs.cells as any).find(
        (item: any) => item.isSelected === true
      )
      //  CTRL/COMMAND + ...
      if ($event.ctrlKey === true || $event.metaKey === true) {
        $event.preventDefault()
        //  按住ctrl/command
        //  meta 对应mac command
        if ($event.key === 'c' || $event.key === 'C') {
          //  复制快捷键
          this.iconCopyCell()
        } else if ($event.key === 'v' || $event.key === 'V') {
          //  粘贴快捷键
          this.iconPasteCell()
        } else if ($event.key === 'x' || $event.key === 'X') {
          //  剪切快捷键
          if (cell !== undefined && this.cells.length > 1) {
            //  当cells length 多于1 才能允许删除
            cell.cutCell()
          }
        } else if ($event.key === 'z' || $event.key === 'Z') {
          if ($event.shiftKey) {
            this.undoRedoOperation('redo')
          } else {
            this.undoRedoOperation('undo')
          }
        } else if ($event.key === 'Enter') {
          //  运行快捷键
          if (cell !== undefined) {
            cell.runCodeCell()
          } else {
            this.operatorRunAll()
          }
        } else if ($event.key === 'ArrowUp' || $event.key === 'ArrowDown') {
          //  有代码块被选中
          if (cell !== undefined) {
            this.runUpOrDownCells($event.key, cell)
          }
          return
        }
      }

      //  delete
      if (
        (this.lastKey === 'd' || this.lastKey === 'D') &&
        ($event.key === 'd' || $event.key === 'D')
      ) {
        //  连续点击d/D
        this.iconDeleteCell()
        this.lastKey = '' //  执行一次删除操作后置空
        isAllowedToSetLastKey = false
      }
      //  ARROW UP/DOWN
      if ($event.key === 'ArrowDown') {
        //  方向键下， 移动cell 聚焦
        if (cell !== undefined) {
          this.focusOtherCell(cell.paraId, 'down')
        }
      } else if ($event.key === 'ArrowUp') {
        //  方向键上， 移动cell 聚焦
        if (cell !== undefined) {
          this.focusOtherCell(cell.paraId, 'up')
        }
      }
      if (isAllowedToSetLastKey) {
        this.lastKey = $event.key
      }
    }
  }
  //  undo redo 去抖操作
  private undoRedoOperation = debounce(
    (action: string) => this._handleUndoRedoOperation(action),
    300
  )

  /**
   * 处理undo redo 操作
   * @param action : string undo / redo
   */
  public async _handleUndoRedoOperation(action: string) {
    let response: any
    if (action === 'undo') {
      response = await undo({
        data: {
          notebookId: this.notebookId,
          id: this.operatorId,
        },
      })
    } else {
      response = await redo({
        data: {
          notebookId: this.notebookId,
          id: this.operatorId,
        },
      })
    }

    if (response.data.code === 100) {
      this.clearOutData()
      this.loadNotebook(this.operatorId)
      this.$message.success(action === 'undo' ? '撤销成功' : '重做成功')
    } else {
      this.$message.error('请求出错')
    }
  }

  /**
   * 清理代码块结果； 向下会包含当前cell
   * @param index
   * @param position 'ArrowUp'/'ArrowDown'
   * @param status pending / init
   */
  public clearCellsResult(index: number, position: string, status: string) {
    if (position === 'ArrowUp') {
      for (let i = 0; i < index; i += 1) {
        this.$set(this.cells[i], 'result', '')
        this.$set(
          this.cells[i],
          'cellStatus',
          status === 'pending' ? '运行中...' : '未运行'
        )
      }
    } else {
      for (let i = index; i < this.cells.length; i += 1) {
        this.$set(this.cells[i], 'result', '')
        this.$set(
          this.cells[i],
          'cellStatus',
          status === 'pending' ? '运行中...' : '未运行'
        )
      }
    }
  }

  /**
   * 向上/向下运行代码块； 向下会包含当前cell
   * @param position 'ArrowUp'/'ArrowDown'
   */
  public async runUpOrDownCells(position: string, cell: any) {
    this.runningStatus = true
    this.runUpAndDownFlag = true
    //  target cell index
    let cellIndex: any
    // eslint-disable-next-line no-restricted-syntax
    for (const i in this.cells) {
      if (this.cells[i].paraId === cell.paraId) {
        cellIndex = i
      }
    }

    this.clearCellsResult(Number(cellIndex), position, 'pending')

    //  总体运行
    await this.saveCode()
    runUpOrDownPara({
      data: {
        pipelineId: this.pipelineId,
        id: this.operatorId,
        notebookId: this.notebookId,
        up: position === 'ArrowUp' ? 1 : -1,
        index: Number(cellIndex),
      },
    })
      .then((response) => {
        if (response.data.code === 100) {
          //  TBD 需要拿到response then 处理
          this.queryRunningStatus(Number(cellIndex), position)
        } else {
          this.runUpAndDownFlag = false
          this.runningStatus = false
          this.clearCellsResult(Number(cellIndex), position, 'init')
          this.$message.error(`${response.data.message}`)
        }
      })
      .catch(() => {
        this.runUpAndDownFlag = false
        this.runningStatus = false
        this.clearCellsResult(Number(cellIndex), position, 'init')
        this.$message.error('运行代码块失败')
      })
  }

  /**
   * 聚焦其他cell
   * @param paraId
   * @param position 'up'/'down'
   */
  public focusOtherCell(paraId: string, position: string) {
    let index = 0
    for (let i = 0; i < this.cells.length; i += 1) {
      if (this.cells[i].paraId === paraId) {
        index = i
        break
      }
    }
    if (position === 'up') {
      if (index > 0) {
        this.onEditorClick(this.cells[index - 1].paraId)
        this.$nextTick(() => {
          const targetElement = document.querySelector('.ele-focus')
          targetElement?.scrollIntoView() // 控制选中的节点滚动到可视区域
        })
      }
    } else if (position === 'down') {
      //  'down'
      if (index < this.cells.length - 1) {
        this.onEditorClick(this.cells[index + 1].paraId)
        this.$nextTick(() => {
          const targetElement = document.querySelector('.ele-focus')
          targetElement?.scrollIntoView() // 控制选中的节点滚动到可视区域
        })
      }
    }
  }

  //  获取vuex 中的被选中的cell
  private get selectedCell() {
    return DataStore.SelectedCell
  }
  //  清除选中的  cell
  public clearSelectedCell() {
    DataStore.setSelectedCell('')
  }

  //  功能列表 - 对应的函数
  public clickFunction(name: string) {
    switch (name) {
      case 'stop':
        this.stopRunning()
        break
      case 'copy':
        this.iconCopyCell()
        break
      case 'paste':
        this.iconPasteCell()
        break
      case 'save':
        this.saveCode()
        break
      default:
        break
    }
  }

  //  点击某个cell 设置选中的cell
  public onEditorClick(paraId: string) {
    DataStore.setSelectedCell(paraId)
  }

  // 改变语言类型
  public typeHandleChange(value: any) {
    this.languageType = value
    // this.timeToCreateOperator()
  }

  // public runnerHandleChange(value: any) {
  //   // this.interpreter = value
  //   this.timeToCreateOperator()
  // }

  /**
   * 创建一个新的notebook, 并且创建初始的cell(paragraph)
   */
  // public async createNotebook() {
  //   const response: any = await createNote({
  //     data: {
  //       name: this.notebookName,
  //       interpreter: 'python',
  //       languageType: this.languageType
  //     },
  //   })

  //   if (response.data.code === 100) {
  //     this.notebookId = response.data.result.notebookId
  //     this.operatorId = response.data.result.id

  //     //  创建初始的cell(paragraph)
  //     const paraId = await this.createPara()
  //     this.$set(this.cells, 0, {
  //       id: 1,
  //       value: '',
  //       paraId,
  //     })
  //   } else {
  //     this.showWarning = true
  //     this.$message.error('请求出错啦')
  //   }
  // }

  /**
   * 创建cell(paragraph)
   * @param code 可选，空的cell or 有默认code 的cell
   */
  public async createPara(index: number, code?: string) {
    const paraResponse = await createParagraph({
      data: {
        notebookId: this.notebookId,
        id: this.operatorId,
        scriptBody: code
          ? `{"text": ${JSON.stringify(
              code
            )}, "index": ${index}, "scriptType": "${
              this.markdownCell(code) ? 'markdown' : 'code'
            }" }`
          : `{"index": ${index}}`,
      },
    })
    if (paraResponse.data.code === 100) {
      return paraResponse.data.result
    }
    this.$message.error('请求出错啦')
    return []
  }

  public markdownCell(code: string) {
    return code.startsWith('md `') && code.endsWith('`')
  }

  /**
   * 保存代码, 不管选中与否，都做全局保存
   */
  public async saveCode() {
    if (this.cells.length === 0) {
      return false
    }
    const body = []
    // eslint-disable-next-line no-restricted-syntax
    for (const i of this.cells) {
      body.push({
        text: i.value,
        paragraphId: i.paraId,
        scriptType: this.markdownCell(i.value) ? 'markdown' : 'code',
      })
    }
    const response = await updateParagraphs({
      data: {
        id: this.operatorId,
        notebookId: this.notebookId,
        scriptBody: JSON.stringify(body),
      },
    })

    if (response.data.code === 100) {
      this.saveTimeline = `autosaved at ${new Date().toString().slice(0, 24)}`

      return true
    }
    this.$message.error('保存失败')
    return false
  }

  /**
   * 自动保存代码（在弹窗出现时开启） 60s 为一个周期
   */
  public autoSaveCode() {
    this.autoSaveTimer = setInterval(() => {
      this.saveCode()
    }, 60000)
  }

  /**
   * 删除代码块 delete, cut
   */
  public async deletePara(paraId: string, index: number) {
    const response = await deleteParagraph({
      data: {
        notebookId: this.notebookId,
        id: this.operatorId,
        paragraphId: paraId,
        index,
      },
    })
    if (response.data.code === 100) {
      return true //  100-success otherwise fail
    }
    this.$message.error('代码删除失败')
    return false
  }

  //  后端api 返回的msg 是数组，默认前几个数组是正确的，若error， 最后一个数组即为出错信息
  public handleCellResultMsg(message: Array<any>) {
    let result = ''
    for (let i = 0; i < message.length; i += 1) {
      if (i < message.length - 1) {
        result += `<span style='color: rgba(0, 0, 0, 0.65);'>${message[i].data}</span>` //  设置正确结果的字体为黑色
      } else {
        result += `<span>${message[i].data}</span>`
      }
    }

    return result
  }

  /**
   * 加载 notebook
   */
  public async loadNotebook(id: number) {
    const response = await queryNotebook({
      data: {
        id,
      },
    })
    if (response.data.code === 100) {
      this.languageType = response.data.result.languageType
      const body = response.data.result.scriptBody
      const temporary: any = JSON.parse(body)

      this.notebookName = temporary.name
      this.notebookId = temporary.id
      const paras = temporary.paragraphs

      // eslint-disable-next-line unicorn/no-for-loop
      for (let i = 0; i < paras.length; i += 1) {
        this.id += 1
        this.$set(this.cells, i, {
          id: this.id,
          value: paras[i]?.text ?? '',
          paraId: paras[i].id,
          result:
            paras[i]?.results?.msg.length > 0 &&
            !(paras[i].text.startsWith('md `') && paras[i].text.endsWith('`'))
              ? this.handleCellResultMsg(paras[i].results.msg)
              : '',
          //  运行结果错误，并且不是md cell 才标记error
          resultType:
            paras[i]?.results?.code === 'ERROR' &&
            !(paras[i]?.text.startsWith('md `') && paras[i]?.text.endsWith('`'))
              ? 'error'
              : 'success',
          // 根据result的值判断是运行完成还是未运行
          cellStatus:
            paras[i]?.results?.msg.length > 0 &&
            !(paras[i].text.startsWith('md `') && paras[i].text.endsWith('`'))
              ? '运行完成'
              : '未运行',
        })
      }
    } else {
      this.$message.error('读取notebook失败')
    }

    if (this.cells.length > 1) {
      this.canDelete = true
    }
  }

  //  在父组件修改cell的运行状态
  public setCellStatusState(cell: any, status: string) {
    this.$set(cell, 'cellStatus', status)
  }

  /**
   * 拖拽事件开始
   */
  public dragCellstart($event: any, index: number) {
    //  所有的都需要添加；
    const element = document.querySelectorAll('.output_wrapper')
    // eslint-disable-next-line no-restricted-syntax
    for (const i of element) {
      i.classList.add('grabbing')
    }
    const elementTwo = document.querySelectorAll('.out_prompt_overlay')
    // eslint-disable-next-line no-restricted-syntax
    for (const i of elementTwo) {
      i.classList.add('grabbing')
    }
    $event.dataTransfer?.setDragImage($event.target, 0, 0)
    this.oldValue = index
  }

  /**
   * 拖拽事件经过
   */
  public dragCellenter($event: any, index: number) {
    $event.preventDefault()
    this.newOld = index
  }

  /**
   * 拖拽事件结束
   */
  public async dragCellend() {
    const element = document.querySelectorAll('.output_wrapper')
    // eslint-disable-next-line no-restricted-syntax
    for (const i of element) {
      i.classList.remove('grabbing')
    }
    const elementTwo = document.querySelectorAll('.out_prompt_overlay')
    // eslint-disable-next-line no-restricted-syntax
    for (const i of elementTwo) {
      i.classList.remove('grabbing')
    }
    //  TBD 移动某个块，而不是互换
    console.log('drag end')
    if (this.oldValue !== this.newOld) {
      const flag = await this.moveCells(
        this.cells[this.oldValue].paraId,
        this.newOld,
        this.oldValue
      )
      if (flag) {
        this.insertAndShift(this.cells, this.oldValue, this.newOld)
      }
    }
  }
  //  配合拖拽实现换位移位
  public insertAndShift(dataArray: Array<Object>, from: number, to: number) {
    const cutOut = dataArray.splice(from, 1)[0] // cut the element at index 'from'
    dataArray.splice(to, 0, cutOut) // insert it at index 'to'
  }
  //  移动cell
  public async moveCells(
    paragraphId: string,
    newIndex: number,
    oldIndex: number
  ) {
    const response = await moveParagraph({
      data: {
        notebookId: this.notebookId,
        id: this.operatorId,
        paragraphId,
        newIndex,
        oldIndex,
      },
    })
    if (response.data.code === 100) {
      return true
    }
    this.$message.error('移位失败')
    return false
  }

  /**
   * 运行代码块
   * @param index 代码块序号
   * @param paraId cell paraId
   * @param value cell value
   */
  public async runCodeCell(index: number, paraId: number, value: string) {
    this.runningStatus = true
    // cell的result置空
    this.$set(this.cells[index], 'result', '')
    // cell的状态改变
    this.$set(this.cells[index], 'cellStatus', '运行中...')
    const response = await runParagraph({
      data: {
        notebookId: this.notebookId,
        id: this.operatorId,
        paragraphId: paraId,
        // scriptBody: JSON.stringify(value),
        scriptBody: value,
        pipelineId: this.pipelineId,
      },
    })

    this.runningStatus = false
    if (response.data.code === 100) {
      const { body } = response.data.result
      if (body.status === 'FINISHED' || body.status === 'ERROR') {
        //  这种情况会处理结果
        //  设置运行结果
        this.$set(
          this.cells[index],
          'result',
          body.results?.msg.length > 0
            ? this.handleCellResultMsg(body.results.msg)
            : ''
        )
        //  配置结果类型
        this.$set(
          this.cells[index],
          'resultType',
          body.results.code === 'ERROR' ? 'error' : 'success'
        )
        this.$message.success('已完成代码块运行')
        //  设置运行状态
        this.$set(this.cells[index], 'cellStatus', '运行完成')
      } else if (body.status === 'ABORT') {
        //  若status为ABORT 则 不处理结果
        this.$message.success('已停止代码块运行')
        //  设置运行状态
        this.$set(this.cells[index], 'cellStatus', '未运行')
      }
    } else {
      this.$message.error(`${response.data.message}`)
      //  设置运行状态
      this.$set(this.cells[index], 'cellStatus', '未运行')
    }
  }

  /**
   * option1 运行所有代码; 需要先总体保存-总体运行-获取总体运行状态
   * option2 运行某个代码快
   */
  public async operatorRunAll() {
    if (this.cells.length === 0) {
      return
    }
    let cellIndex: any = 0
    const cell = (this.$refs.cells as any).find((item: any) => {
      return item.isSelected === true
    })
    if (cell !== undefined) {
      //  运行某个cell
      if (cell.value.startsWith('md `') && cell.value.endsWith('`')) {
        //  若该cell 为文本块，
        cell.runCodeCell()
      } else {
        //  获取对应的cellIndex
        // eslint-disable-next-line no-restricted-syntax
        for (const i in this.cells) {
          if (this.cells[i].paraId === cell.paraId) {
            cellIndex = i
          }
        }
        this.runCodeCell(cellIndex, cell.paraId, cell.value)
      }
    } else {
      this.runningStatus = true
      // cell的result置空
      this.cells.forEach((cellIter: any) => {
        this.$set(cellIter, 'result', '')
        this.$set(cellIter, 'cellStatus', '运行中...')
      })
      //  总体运行
      await this.saveCode()
      runNotebook({
        data: {
          id: this.operatorId,
          notebookId: this.notebookId,
          pipelineId: this.pipelineId,
        },
      })
        .then((response) => {
          if (response.data.code === 100) {
            //  TBD 需要拿到response then 处理
            this.queryRunningStatus()
          } else {
            this.runningStatus = false
            this.$message.error(`${response.data.message}`)
            // cell的运行状态改成未运行
            this.cells.forEach((cellIter: any) => {
              this.$set(cellIter, 'cellStatus', '未运行')
            })
          }
        })
        .catch(() => {
          this.runningStatus = false
          this.$message.error('运行代码块失败')
          // cell的运行状态改成未运行
          this.cells.forEach((cellIter: any) => {
            this.$set(cellIter, 'cellStatus', '未运行')
          })
        })
    }
  }
  /**
   * 在全部运行代码之后 query cell status
   * 改造： 适应向上/向下运行cells
   * @param index 运行的位置
   * @param position  向上/向下  ArrowUp ArrowDown
   */
  public async queryRunningStatus(index?: number, position?: string) {
    const that = this as any
    that.queryTimer = setInterval(async () => {
      let stopFlag = true
      const respRunStatus = await queryRunningStatus({
        data: {
          id: this.operatorId,
          notebookId: this.notebookId,
        },
      })
      if (respRunStatus.data.code === 100) {
        const paras = respRunStatus.data.result.body.paragraphs
        let i: any = 0
        let { length } = paras

        //  向上向下运行cells
        if (position === 'ArrowUp') {
          length = index
        } else if (position === 'ArrowDown') {
          i = index
        }

        // eslint-disable-next-line unicorn/no-for-loop
        for (i; i < length; i += 1) {
          //  如果其中一个cell 为error, 不会继续执行
          if (paras[i].status === 'ERROR') {
            that.runningStatus = false
            clearInterval(that.queryTimer)
          }
          //  状态finished
          if (paras[i].status === 'FINISHED' || paras[i].status === 'ERROR') {
            if (
              !(
                paras[i].text.startsWith('%md\nmd `') &&
                paras[i].text.endsWith('`')
              ) //  语句是md text
            ) {
              this.$set(
                this.cells[i],
                'result',
                paras[i]?.results?.msg.length > 0
                  ? this.handleCellResultMsg(paras[i].results.msg)
                  : ''
              )
              this.$set(
                this.cells[i],
                'resultType',
                paras[i].results.code === 'ERROR' ? 'error' : 'success'
              )
              this.$set(this.cells[i], 'cellStatus', '运行完成')
            } else {
              // refs [] runCodeCell
              const cells = this.$refs.cells as any
              cells[i].runCodeCell()
            }
          } else {
            stopFlag = false
          }
        }
        if (stopFlag) {
          this.$message.success('已完成所有代码块运行')
          that.runningStatus = false
          this.runUpAndDownFlag = false
          clearInterval(that.queryTimer)
        }
      } else {
        this.$message.error('读取notebook失败')
      }
    }, 2000)
  }

  /**
   * 更新cell 中value 值 - 根据cm editor text 变化
   */
  public updateValue(index: number, value: string) {
    //  查找数组中某个值
    this.cells[index].value = value
  }

  /**
   * 删除代码块
   */
  public async deleteCell(paraId: string, index: number) {
    const ifSuccess = await this.deletePara(paraId, index)
    if (ifSuccess) {
      for (let i = 0; i < this.cells.length; i += 1) {
        if (this.cells[i].paraId === paraId) {
          this.cells.splice(i, 1)
          break
        }
      }
    }

    if (this.cells.length === 1) {
      this.canDelete = false
    }
  }

  public async deleteEmptyCell() {
    let emptyIndex = null
    // 若连续添加，前面的value 为空，那么不再重复添加
    for (let i = 0; i < this.cells.length; i += 1) {
      if (this.cells[i].value === '') {
        emptyIndex = i
        break
      }
    }
    if (emptyIndex !== null) {
      const ifSuccess = await this.deletePara(
        this.cells[emptyIndex].paraId,
        Number(emptyIndex)
      )
      if (ifSuccess) {
        this.cells.splice(emptyIndex, 1)
      } else {
        return false
      }
    }
    return true
  }

  /**
   * 添加代码块 rules:
   * 不能无限添加； 存在前置添加/后置添加； 仅存在一个空的cell;
   * 若cell 有输入虽没保存，还是可以创建新的cell
   */
  public async addCell(id: number, position: string) {
    console.log(position, 'position')
    //  初始状态下持续添加，不起作用 -
    if (
      this.cells[this.cells.length - 1].value === '' &&
      this.cells.length === 1
    ) {
      return
    }
    //  若触发添加的cell value 为‘’, 直接跳过
    const cell = this.cells.find((item: any) => item.id === id)
    if (cell.value === '') {
      return
    }

    const flag = await this.deleteEmptyCell()
    if (!flag) {
      return
    }

    this.id += 1
    const index = this.findNewCellIndex(id, position)
    const paraId = await this.createPara(index)
    //  聚焦该cell
    DataStore.setSelectedCell(paraId)

    this.insertCell(id, paraId, position, '')

    if (this.cells.length > 1) {
      this.canDelete = true
    }
  }
  //  停止运行 - 1. 单个cell 停止 2. 整体运行停止
  public async stopRunning() {
    this.runningStatus = false
    const cell = (this.$refs.cells as any).find((item: any) => {
      return item.isSelected === true
    })
    if (cell !== undefined && !this.runUpAndDownFlag) {
      //  关闭interval - 应对向上/向下执行cells
      clearInterval(this.queryTimer)
      //  stop某个cell
      const response = await stopPara({
        data: {
          id: this.operatorId,
          notebookId: this.notebookId,
          paragraphId: cell.paraId,
        },
      })
      if (response.data.code === 100) {
        this.$message.success('已停止代码块运行')
      } else {
        this.$message.error('停止代码块运行失败')
      }
    } else {
      //  stop all cells
      //  关闭interval
      clearInterval(this.queryTimer)
      this.runUpAndDownFlag = false

      const response = await stopAllPara({
        data: {
          id: this.operatorId,
          notebookId: this.notebookId,
        },
      })
      if (response.data.code === 100) {
        this.$message.success('已停止全部代码块运行')
        //  修改运行中的代码状态为未运行
        this.cells.forEach((cellIter: any) => {
          //  三种状态 未运行、运行中、运行完成
          if (cellIter.cellStatus === '运行中...') {
            cellIter.cellStatus = '未运行'
          }
        })
      } else {
        this.$message.error('停止全部代码块运行失败')
      }
    }
  }

  /**
   * copy cell value， 当 cell 选中有效
   */
  public iconCopyCell() {
    if (this.cells.length === 0) {
      return
    }
    const cell = (this.$refs.cells as any).find(
      (item: any) => item.isSelected === true
    )
    if (cell !== undefined) {
      this.$message.success('复制成功')
      DataStore.setCopiedCellValue(cell.value)
    }
  }

  /**
   * paste cell value， 当cell 选中有效
   */
  public async iconPasteCell() {
    if (this.cells.length === 0) {
      return
    }

    const cell = (this.$refs.cells as any).find(
      (item: any) => item.isSelected === true
    )
    if (cell !== undefined) {
      this.pasteCell(cell.dataNodeId, this._copiedCodeValue, cell.value)
    }
  }

  /**
   * delete cell value， 当cell 选中有效
   */
  public iconDeleteCell() {
    if (this.cells.length === 1) {
      //  当只有一个cell 时， 不允许删除；
      return
    }
    const cell = (this.$refs.cells as any).find(
      (item: any) => item.isSelected === true
    )
    let cellIndex
    //  获取对应的cellIndex
    // eslint-disable-next-line no-restricted-syntax
    for (const i in this.cells) {
      if (this.cells[i].paraId === cell.paraId) {
        cellIndex = i
      }
    }
    if (cell !== undefined) {
      console.log(cell, 'cell')
      this.$message.success('删除成功')
      this.deleteCell(cell.paraId, Number(cellIndex))
    }
  }

  /**
   * 粘贴代码块， 在特定id cell 之后插入, 插入的cell copyValue, 特定id cell 的value
   */
  public async pasteCell(id: number, copyValue: string, value: string) {
    //  当粘贴的cell value = ', 并且被粘贴的cell value = ''， 则不进行操作
    if (value === '' && copyValue === '') {
      return
    }

    //  如果粘贴的cell 为空，需要先删除已有的空的cell; 反之，粘贴的cell 不为空，则不需要删除空的cell 操作；
    if (copyValue.length === 0) {
      const flag = await this.deleteEmptyCell()
      if (!flag) {
        return
      }
    }

    this.id += 1
    const index = this.findNewCellIndex(id, 'after')
    const paraId = await this.createPara(index, copyValue)
    //  聚焦该cell
    DataStore.setSelectedCell(paraId)
    this.$message.success('粘贴成功')

    this.insertCell(id, paraId, 'after', copyValue)

    if (this.cells.length > 1) {
      this.canDelete = true
    }
  }

  public findNewCellIndex(id: number, position: string) {
    const { length } = this.cells
    let index = 0
    for (let j = 0; j < length; j += 1) {
      if (this.cells[j].id === id) {
        index = position === 'after' ? j + 1 : j
        break
      }
    }
    return index
  }

  //  在id cell 的pos 位置 插入paraId cell
  public insertCell(
    id: number,
    paraId: string,
    pos: string,
    value: string = ''
  ) {
    const { length } = this.cells
    for (let j = 0; j < length; j += 1) {
      if (this.cells[j].id === id) {
        this.cells.splice(pos === 'after' ? j + 1 : j, 0, {
          id: this.id,
          value,
          paraId,
        })
        // if (j !== length - 1 || pos === 'before') {      在create 的时候就已经带有移位信息；
        //   //  后端默认添加cell 在末尾，所以不再末尾的添加需要进行移位
        //   //  move cell to the right position
        //   this.moveCells(paraId, pos === 'after' ? j + 1 : j)
        // }
        break
      }
    }
  }
  //  点击model - 确认
  public async handleOk() {
    if (!(this.cells.length === 1 && this.cells[0].value === '')) {
      const response = await this.saveCode()
      if (response) {
        //  保存成功之后，call 导出算子的api
        const result = await exportOperator({
          data: {
            id: this.operatorId,
            pipelineId: this.pipelineId,
            notebookId: this.notebookId,
          },
        })
        if (result.data.code === 100) {
          this.$message.success('算子导出成功')
          this.confirmSetParams()
        } else {
          this.$message.error(`${result.data.message}`)
          return
        }
      }
    }
    this.clearOutData()
    this.closePop()
  }

  /**
   * 取消编辑
   */
  public handleCancel() {
    this.clearOutData()
    this.closePop()
    DataStore.setEditOperatorNode(null)
  }

  public clearOutData() {
    this.notebookName = ''
    this.languageType = ''
    this.cells = []
    this.saveTimeline = ''
  }

  /**
   * 关闭弹框
   */
  public closePop() {
    this.$emit('close-pop')
  }

  /**
   * 重命名字段
   */
  private async onNameChange(event: KeyboardEvent) {
    const name = (<HTMLInputElement>event?.target)?.value.trim()
    this.isEditingOperatorName = false
    if (this.notebookName === name) {
      return
    } // 若值没有变化，不会发起rename
    // 若值为空，则无法保存
    if (name === '') {
      this.isEditingOperatorName = true
      this.$message.error('名字不允许为空！')
      return
    }
    const paraResponse = await renameNotebook({
      data: {
        taskId: this.taskId,
        id: this.operatorId,
        name,
      },
    })
    if (paraResponse.data.code === 100) {
      this.$message.success('重命名成功')
      this.notebookName = name

      // 更新pipeline
      DataStore.queryPipelineNodes({
        id: this.editNode.projectId,
        pipelineId: this.editNode.pipelineId,
      })
    } else {
      this.isEditingOperatorName = true
      this.$message.error('名字重复，请重新命名')
    }
  }
  /**
   * 确认编辑参数
   */
  public confirmSetParams() {
    const setParameters: any = { setParams: [] }
    const dataJson: any = {}
    Object.keys(setParameters).forEach((key: string) => {
      dataJson[key] = setParameters[key]
      dataJson.algType = this.editNode.data.algType
    })
    const parameters = {
      id: this.editNode.id,
      name: this.notebookName,
      projectId: this.editNode.projectId,
      pipelineId: this.editNode.pipelineId,
      parentId: this.editNode.parentId,
      childId: this.editNode.childId,
      type: this.editNode.type,
      data: dataJson,
    }
    this.nodeUpdate(parameters)
  }
  /**
   * 节点参数更新
   * @param parameters
   */
  public async nodeUpdate(parameters: any) {
    simpleUpdate({ data: parameters })?.then((response: any) => {
      if (response.data.code === 100) {
        DataStore.setConfirmEditTime(new Date().getTime())
        // 更新pipeline
        DataStore.queryPipelineNodes({
          id: this.editNode.projectId,
          pipelineId: this.editNode.pipelineId,
        })
      }
    })
  }
}
</script>

<style lang="less" scoped>
/deep/ .ant-modal {
  padding-bottom: 0 !important;
}

/deep/ .ant-modal-body {
  padding: 0;
}

.configure-area {
  background-color: #f3f3f7;
  display: flex;
  justify-content: space-between;
  padding: 15px 24px;

  .name-area {
    align-items: center;
    display: flex;
    line-height: 1;
    min-width: 300px;

    &:hover {
      .edit-btn {
        font-size: 15px;
        visibility: visible;
      }
    }
  }

  .operator-name {
    display: inline-block;
    font-weight: 600;
    height: 24px;
    line-height: 24px;
    margin-right: 30px;
    max-width: 320px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    width: auto;
  }

  .save-time-box {
    color: #222432;
    font-size: 12px;
    margin-left: 20px;
    opacity: 0.5;
  }

  .edit-btn {
    visibility: hidden;
  }

  .selector {
    margin: auto 0 auto 10px;
    width: 100px;
  }
}

.divider-line {
  background: linear-gradient(0deg, #e9e9e9 33%, #fff 50%);
  background-color: #e9e9e9;
  height: 3px;
  margin: 0 20px;
  overflow: hidden;
  padding: 0;
}

.progress-bar {
  animation: gradient 1.5s ease infinite;
  background: linear-gradient(90deg, #c0bfff78 25%, #5760e6 37%, #c0bfff78 63%);
  background-size: 400% 100%;
  height: 3px;
}

@keyframes gradient {
  0% {
    background-position: 100% 50%;
  }

  100% {
    background-position: 0% 50%;
  }
}

.left-btn {
  align-items: center;
  display: flex;
  height: 30px;
  margin: 5px 20px;

  .btn {
    align-items: center;
    border-radius: 2px;
    cursor: pointer;
    display: flex;
    margin-right: 5px;
    padding: 5px;
    position: relative;

    &:hover {
      background-color: #eeeffd;
      color: #6973ff;
    }
  }

  .divider {
    border-right: 1px solid #5d637e;
    height: 16px;
    margin: 0 10px 0 5px;
    width: 1px;
  }

  .function-btn-spacing {
    color: #5d637e;
    font-size: 18px;
  }
}

.editor-area {
  height: calc(100vh - 307px);
  margin: 20px;
  overflow-y: scroll;
}

.flip-list-move {
  transition: transform 0.3s ease-in;
}

.warning-text {
  color: #e6505a;
  display: inline-block;
  margin: 0;
}

.configure-text {
  margin-bottom: 0.5em;
  margin-top: 1em;
}

.right-menu {
  align-items: center;
  display: flex;
  justify-content: center;
}

.edit-operator-tips {
  color: #e74c3c;
  cursor: pointer;
  display: flex;
  font-size: 20px;
  padding: 0 12px;
}
</style>

<style>
.ant-message {
  z-index: 1060;
}
</style>
